﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using StripeOne.ShortenUrl.API.Common;
using System.Net;
using System.Diagnostics;
using System.IO;
using StripeOne.ShortenUrl.Model;
using System.Web;

namespace StripeOne.ShortenUrl.API.Client
{
    public class BaseClient
    {
        private readonly string _serviceRootUrl;
        private readonly string _consumerKey;
        private readonly string _consumerSecret;
        private readonly bool _useSignature;
        private readonly bool _alwaysSendIpAddress;
        private readonly int _language;
        
        public BaseClient(string serviceRootUrl)
        {
            this._serviceRootUrl = serviceRootUrl;
            this._consumerKey = "";
            this._consumerSecret = "";
            this._useSignature = false;
            this._alwaysSendIpAddress = false;
            this._language = 1;
        }

        public BaseClient(string serviceRootUrl, string consumerKey, string consumerSecret)
        {
            this._serviceRootUrl = serviceRootUrl;
            this._consumerKey = consumerKey;
            this._consumerSecret = consumerSecret;
            this._useSignature = true;
            this._alwaysSendIpAddress = true;
        }

        public BaseClient(string serviceRootUrl, string consumerKey, string consumerSecret, int language)
        {
            this._serviceRootUrl = serviceRootUrl;
            this._consumerKey = consumerKey;
            this._consumerSecret = consumerSecret;
            this._useSignature = true;
            this._alwaysSendIpAddress = true;
            this._language = language;
        }

        public BaseClient(string serviceRootUrl, string consumerKey, string consumerSecret, bool useSignature)
        {
            this._serviceRootUrl = serviceRootUrl;
            this._consumerKey = consumerKey;
            this._consumerSecret = consumerSecret;
        }

        public BaseClient(string serviceRootUrl, string consumerKey, string consumerSecret, int language, bool useSignature)
        {
            this._serviceRootUrl = serviceRootUrl;
            this._consumerKey = consumerKey;
            this._consumerSecret = consumerSecret;
            this._useSignature = useSignature;
            this._alwaysSendIpAddress = true;
            this._language = language;
        }

        public BaseClient(string serviceRootUrl, string consumerKey, string consumerSecret, bool useSignature, bool alwaysSendIpAddress)
        {
            this._serviceRootUrl = serviceRootUrl;
            this._consumerKey = consumerKey;
            this._consumerSecret = consumerSecret;
            this._useSignature = useSignature;
            this._alwaysSendIpAddress = alwaysSendIpAddress;
        }

        protected ShortenApiResult<T> ExecuteApiCall<T, TS>(string serviceName, string methodName, Dictionary<string, object> parameters, ShortenApiMethod method)
        {
            long responseMs, transferMs;
            return (ExecuteApiCall<T, TS>(serviceName, methodName, parameters, method, out responseMs, out transferMs));
        }

        protected ShortenApiResult<T> ExecuteApiCall<T, TS>(string serviceName, string methodName, Dictionary<string, object> parameters, ShortenApiMethod method, out long responseMs, out long transferMs)
        {
            //MibClient2.Log.MibLog.Default.Verbose("Starting call to " + serviceName + "/" + methodName);

            var json = ExecuteApiCallCache<TS>(serviceName, methodName, parameters, method, out responseMs, out transferMs);

            var ret = Deserialize<ShortenApiResult<T>, TS>(json);
            //MibClient2.Log.MibLog.Default.Verbose("Finished call to " + serviceName + "/" + methodName);

            return (ret);
        }

        private string ExecuteApiCallCache<TS>(string serviceName, string methodName, Dictionary<string, object> parameters, ShortenApiMethod method, out long responseMs, out long transferMs)
        {
            var json = "";
            //TODO: implementar cache
            //int cacheTime = this.GetCacheTime(methodName);
            //string cacheKey = this.GetCacheKey(serviceName, methodName, parameters, usePost);
            //object objCache = MibCache.GetObject(cacheKey);
            //if (objCache is String)
            //{
            //    responseMs = 0;
            //    transferMs = 0;
            //    json = MibParser.ToString(objCache);
            //}
            //else
            //{
            json = ExecuteApiCallNonCached<TS>(serviceName, methodName, parameters, method, out responseMs, out transferMs);
            //MibCache.Add(json, cacheKey, cacheTime);
            //}

            return json;
        }

        private string ExecuteApiCallNonCached<TS>(string serviceName, string methodName, Dictionary<string, object> parameters, ShortenApiMethod method, out long responseMs, out long transferMs)
        {
            try
            {
                // Request Url
                string requestUrl;
                this.GetRequest<TS>(serviceName, methodName, parameters, method, out requestUrl, out parameters);
                var httpRequest = (HttpWebRequest)WebRequest.Create(requestUrl);
                if (method == ShortenApiMethod.POST || method == ShortenApiMethod.PUT || method == ShortenApiMethod.DELETE)
                {
                    httpRequest.Method = method.ToString();
                    httpRequest.ContentType = "application/json";
                    httpRequest.Timeout = 350000000;
                    httpRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
                    byte[] requestBytes = Encoding.UTF8.GetBytes(Serialize<Dictionary<string, object>, TS>(parameters));
                    httpRequest.ContentLength = requestBytes.Length;

                    using (var requestStream = httpRequest.GetRequestStream())
                    {
                        requestStream.Write(requestBytes, 0, requestBytes.Length);
                    }
                }
                else
                {
                    var getParameters = BuildQueryString(parameters);
                    httpRequest = (HttpWebRequest)WebRequest.Create(requestUrl + "?" + getParameters);
                    httpRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
                    httpRequest.Method = "GET";
                }

                string json = string.Empty;
                var watch = new Stopwatch();
                watch.Start();
                using (var response = httpRequest.GetResponse() as HttpWebResponse)
                {
                    watch.Stop();
                    responseMs = watch.ElapsedMilliseconds;
                    watch.Reset();
                    watch.Start();
                    if (response != null)
                        using (var responseStream = response.GetResponseStream())
                        {
                            if (responseStream != null)
                                using (var responseStreamReader = new StreamReader(responseStream))
                                {
                                    json = responseStreamReader.ReadToEnd();
                                }
                        }
                }
                watch.Stop();
                transferMs = watch.ElapsedMilliseconds;
                return (json);
            }
            catch (Exception ex)
            {
                //MibClient2.Log.MibLog.Default.Exception(ex);
                throw;
            }
        }

        private string BuildQueryString(IEnumerable<KeyValuePair<string, object>> parameters)
        {
            var queryString = "";
            var separator = "";
            foreach (var item in parameters)
            {
                if (queryString != "")
                    separator = "&";
                queryString += separator + item.Key + "=" + HttpUtility.UrlEncode(item.Value.ToString());
            }
            return queryString;
        }

        private void GetRequest<TS>(string serviceName, string methodName, Dictionary<string, object> parameters, ShortenApiMethod method, out string requestUrl, out Dictionary<string, object> requestParameters)
        {

            // Request Url
            requestUrl = this._serviceRootUrl + "/" + serviceName + "/" + methodName;

            // Request Parameters

            // Consumer key must always be present
            requestParameters = parameters;
            requestParameters.Add("language", this._language);
        }

        private int GetCacheTime(string methodName)
        {
            var cacheTime = 0;
            //TODO: Implementar Cache
            //if (methodName != null)
            //{
            //    cacheTime = new GeneralCacheConfig().GetCacheTime(methodName);
            //}
            return (cacheTime);
        }

        private string GetCacheKey(string serviceName, string methodName, Dictionary<string, object> parameters, bool usePost)
        {
            var parameterString = String.Join("&", parameters.OrderBy(k => k.Key).Select(k => k.Key + "=" + k.Value).ToArray()) + "_" + serviceName + "_" + methodName + "_" + usePost.ToString().ToLower();
            return ("StripeOneApiClientCache_" + parameterString.GetHashCode() + "_" + parameterString.Length);
        }

        #region Helpers

        // Uses the same serialzier/deserializer as WCF, allowing user of the "KnownClass" attributes 
        private static T Deserialize<T, TS>(string json)
        {
            return ShortenClassSerializer.Deserialize<T>(json);
        }

        private static string Serialize<T, TS>(T concreteObject)
        {
            return ShortenClassSerializer.Serialize(concreteObject, true);
        }

        private void ThrowExceptionFor(StatusCode statusCode, string statusMessage)
        {
            if (statusCode == StatusCode.UnknownError) throw new Exception(statusMessage);
            StatusCodeHelper.ThrowExceptionFor(statusCode, statusMessage);
        }

        #endregion
    }
}
